package com.shuai.gateway.filter;


import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import com.shuai.common.constant.AuthenticationConstant;
import com.shuai.common.constant.ErrorCode;
import com.shuai.common.constant.UserConstant;
import com.shuai.common.exception.BusinessException;
import com.shuai.common.model.InnerUser;
import com.shuai.common.service.InnerUserService;
import com.shuai.common.utils.MyJWTUtil;
import com.shuai.gateway.config.AuthProperties;
import com.shuai.gateway.manager.RandomManager;
import com.shuai.gateway.utils.AuthenticationSignUtils;
import com.shuai.gateway.utils.TimestampUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.time.LocalDateTime;
import java.util.Date;

@Component
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private AuthProperties authProperties;


    @Autowired
    private RandomManager randomManager;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @DubboReference
    private InnerUserService innerUserService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 记录请求日志
        this.requestLog(request);
        String path = request.getPath().toString();
        if (this.exclude(path)) {
            return chain.filter(exchange);
        }
        if (path.startsWith("/api")) {
            this.doAuthenticationForManageSys(request);
        } else {
            // 根据请求做鉴权
            this.doAuthenticationForApiPlatform(request);
        }
        return chain.filter(exchange);
    }

    private boolean exclude(String path) {
        return authProperties.getExcludeUri().contains(path);
    }


    private void doAuthenticationForManageSys(ServerHttpRequest request) {
        HttpHeaders requestHeaders = request.getHeaders();
        String authentication = requestHeaders.getFirst(AuthenticationConstant.AUTHENTICATION_HEADER);
        if (CharSequenceUtil.isBlank(authentication)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限,请登录");
        }
        if (!MyJWTUtil.verifyToken(authentication)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限,请登录");
        }
        // 校验JWT过期时间
        if (MyJWTUtil.getClaims(authentication).getExpiration().before(new Date())) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限,请登录");
        }
        Object payLoadItem = MyJWTUtil.getPayLoadItem(authentication, "userId");
        if (payLoadItem == null) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限，请登录");
        }
        String userId = payLoadItem.toString();
        String userJson = stringRedisTemplate.opsForValue().get(UserConstant.USER_ATTRIBUTE_REDIS_KEY + userId);
        if (CharSequenceUtil.isBlank(userJson)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无权限,请登录");
        }
    }

    private void doAuthenticationForApiPlatform(ServerHttpRequest request) {
        HttpHeaders requestHeaders = request.getHeaders();
        String API_KEY = requestHeaders.getFirst(AuthenticationConstant.API_KEY);
        String SIGN = requestHeaders.getFirst(AuthenticationConstant.SIGN);
        String RANDOM = requestHeaders.getFirst(AuthenticationConstant.RANDOM);
        String TIMESTAMP = requestHeaders.getFirst(AuthenticationConstant.TIMESTAMP);
        String BODY = requestHeaders.getFirst(AuthenticationConstant.BODY);

        // 校验请求头中需要的参数是否都存在
        if (SIGN == null
                || RANDOM == null
                || TIMESTAMP == null
                || API_KEY == null ||
                org.apache.commons.lang3.StringUtils.isAnyBlank(API_KEY, SIGN, RANDOM, TIMESTAMP)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        // 判断随机数是否已经出现过
        long random = Long.parseLong(RANDOM);
        if (randomManager.existsVal(API_KEY, random)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        } else {
            randomManager.putVal(API_KEY, random);
        }

        // 判断时间戳，验证是否是最近是最近5分钟内的请求
        long timestamp = Long.parseLong(TIMESTAMP);
        if (!TimestampUtils.inTimeRange(timestamp)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        //根据API-KEY查询用户是否存在并带回用户信息
        InnerUser userInfo = innerUserService.getUserInfo(API_KEY);
        if (userInfo == null) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "未分配的API-KEY！");
        }
        String API_SECRET = userInfo.getApiSecret();
        if (StringUtils.isBlank(API_SECRET)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        // 生成签名，比较签名是否相同
        if (!AuthenticationSignUtils.generateSign(API_KEY, API_SECRET, timestamp, BODY).equals(SIGN)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
    }


    /**
     * 请求日志记录
     */
    private void requestLog(ServerHttpRequest request) {
        RequestPath path = request.getPath();
        String requestId = request.getId();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        InetSocketAddress localAddress = request.getLocalAddress();
        HttpMethod method = request.getMethod();
        StringBuilder builder = new StringBuilder();
        builder.append("\n\n 以下是请求：【").append(requestId).append("】的日志输出：");
        builder.append("\n 请求时间：").append(LocalDateTime.now());
        builder.append("\n 请求方法：").append(method);
        builder.append("\n 请求发送地址：").append(localAddress);
        builder.append("\n 请求路径：").append(path);
        builder.append("\n 请求参数：").append(JSONUtil.toJsonStr(queryParams));
        builder.append("\n");
        log.info(builder.toString());
    }

    /**
     * 过滤器的优先级
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}